// Copyright © 2023 Cisco Systems, Inc. and its affiliates.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package exploits

import (
	"context"
	"fmt"
	"strings"

	"github.com/openclarity/openclarity/core/log"
	"github.com/openclarity/openclarity/core/to"
	"github.com/openclarity/openclarity/scanner/common"
	"github.com/openclarity/openclarity/scanner/families"
	"github.com/openclarity/openclarity/scanner/families/exploits/types"
	vulnerabilitytypes "github.com/openclarity/openclarity/scanner/families/vulnerabilities/types"
	"github.com/openclarity/openclarity/scanner/internal/scan_manager"
)

type Exploits struct {
	conf types.Config
}

func New(conf types.Config) families.Family[*types.Result] {
	return &Exploits{
		conf: conf,
	}
}

func (e Exploits) GetType() families.FamilyType {
	return families.Exploits
}

func (e Exploits) Run(ctx context.Context, store families.ResultStore) (*types.Result, error) {
	logger := log.GetLoggerFromContextOrDiscard(ctx)

	if e.conf.InputFromVuln {
		logger.Infof("Using input from Vulnerabilities results")

		vulnerabilities, err := families.GetFamilyResultByType[*vulnerabilitytypes.Result](store)
		if err != nil {
			return nil, fmt.Errorf("failed to get vulnerabilities results: %w", err)
		}

		cveIDs := getCVEIDsFromVulnerabilitiesResults(vulnerabilities)

		e.conf.Inputs = append(e.conf.Inputs, common.ScanInput{
			Input:     cveIDs,
			InputType: common.CSV,
		})
	}

	// Run all scanners using scan manager
	manager := scan_manager.New(e.conf.ScannersList, e.conf.ScannersConfig, Factory)
	scans, err := manager.Scan(ctx, e.conf.Inputs)
	if err != nil {
		return nil, fmt.Errorf("failed to process inputs for exploits: %w", err)
	}

	exploits := types.NewResult()

	// Merge results
	for _, scan := range scans {
		logger.Infof("Merging result from %q", scan)

		exploits.Merge(scan.Info, scan.Result)
	}

	return exploits, nil
}

// create a comma separated representation of cveIDs array, as an input for the exploits scanners.
func getCVEIDsFromVulnerabilitiesResults(result *vulnerabilitytypes.Result) string {
	if result == nil {
		return ""
	}

	cvesMap := make(map[string]bool)
	for _, vulnerability := range result.VulnerabilitiesByKey {
		cvesMap[vulnerability.ID] = true
	}

	cves := strings.Join(to.Keys(cvesMap), ",")

	return cves
}
